package com.hybrid.jsbridge;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import com.hybrid.thread.WVThreadPool;
import com.hybrid.utils.TaoLog;
import com.hybrid.webview.WVWebView;

/**
 * 在此写用途
 *
 * @author: zhiwei
 * @date: 2017-10-09 22:50
 * @version: 9.1.0
 */
public class WVJsBridge implements Handler.Callback {
    private static final String TAG = "WVJsBridge";
    private static final Pattern pattern = Pattern.compile("hybrid://(.+?):(.+?)/(.+?)(\\?(.*?))?");

    public static final int CALL_EXECUTE = 0x0;
    public static final int CALL_METHOD = 0x1;
    public static final int NO_METHOD = 0x2;
    public static final int NO_PERMISSION = 0x3;
    public static final int CLOSED = 0x4;

    private static WVJsBridge mJsBridge;
    private static Handler mHandler;

    private boolean enabled = true;

    private boolean mSkipPreprocess;

    private boolean isInit = false;

    public synchronized static WVJsBridge getInstance() {
        if (mJsBridge == null) {
            mJsBridge = new WVJsBridge();
        }
        return mJsBridge;
    }

    private WVJsBridge() {
        mHandler = new Handler(Looper.getMainLooper(), this);
    }

    /**
     * 已初始化内置的jsbridge的判定，全局可用
     */
    public synchronized void init() {
        isInit = true;
    }

    public void skipPreprocess() {
        this.mSkipPreprocess = true;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * 通过hybrid 协议解析调用, 在 onJsPrompt 方法中拦截请求
     *
     * @param url
     *            格式如：hybrid://WVLocation:1234556/getLocation?{"v":"1.0"}
     */
    public void callMethod(WVWebView webView, String url) {
        if (TaoLog.getLogStatus()) {
            TaoLog.d(TAG, "callMethod: url=" + url);
        }
        if (!isInit) {
            TaoLog.w(TAG, "jsbridge is not init.");
            return;
        }
        final WVCallMethodContext request = getRequest(url);
        if (request == null) {
            TaoLog.w(TAG, "url format error and call canceled. url=" + url);
            return;
        } else {
            request.webview = webView;
        }
        final String pageUrl = webView.getUrl();
        WVThreadPool.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                callMethod(request, pageUrl);
            }
        });
    }

    private void callMethod(WVCallMethodContext callmethod, String pageUrl) {
        if (TaoLog.getLogStatus()) {
            String log = String.format("callMethod-obj:%s method:%s param:%s sid:%s", callmethod.objectName, callmethod.methodName,
                callmethod.params, callmethod.token);
            TaoLog.d(TAG, log);
        }
        // 埋点数据。对于别名，此处上报的是转换前的名称
        //if (WVMonitorService.getJsBridgeMonitor() != null) {
        //    WVMonitorService.getJsBridgeMonitor().didCallAtURL(callmethod.objectName, callmethod.methodName, pageUrl);
        //}
        // Turned off
        if (!enabled || callmethod.webview == null) {
            TaoLog.w(TAG, "jsbridge is closed.");
            startCall(CLOSED, callmethod);
            return;
        }

        if (!mSkipPreprocess) {
            /**
             * JS API的异步和同步鉴权机制
             */
            if (WVJsbridgeService.getJSBridgePreprocessors() != null && WVJsbridgeService.getJSBridgePreprocessors().isEmpty()) {
                for (final WVJSAPIAuthCheck preprocessor : WVJsbridgeService.getJSBridgePreprocessors()) {
                    if (!preprocessor.apiAuthCheck(pageUrl, callmethod.objectName, callmethod.methodName, callmethod.params)) {
                        TaoLog.w(TAG, "preprocessor call fail, callMethod cancel.");
                        startCall(NO_PERMISSION, callmethod);
                        return;
                    }

                }
            }
            /**
             * TODO：js APi 异步鉴权机制
             */
            if (WVJsbridgeService.getJSBridgeayncPreprocessors() != null && WVJsbridgeService.getJSBridgeayncPreprocessors().isEmpty()) {
                for (final WVAsyncAuthCheck preprocessor : WVJsbridgeService.getJSBridgeayncPreprocessors()) {
                    if (preprocessor.AsyncapiAuthCheck(pageUrl, callmethod, new WVAsyncAuthCheckCallBackforJsBridge())) {
                        TaoLog.w(TAG, "enter  WVAsyncAuthCheck preprocessor  ");
                        return;
                    }

                }
            }
        }
        aftercallMethod(callmethod, pageUrl);
    }

    public static void aftercallMethod(WVCallMethodContext callmethod, String pageUrl) {
        /**
         * alias转换
         */
        Map<String, String> origPlugin = WVPluginManager.getOriginalPlugin(callmethod.objectName, callmethod.methodName);
        if (origPlugin != null) {
            if (TaoLog.getLogStatus()) {
                TaoLog.i(TAG, "call method through alias name. newObject: " + origPlugin.get(WVPluginManager.KEY_NAME) + " newMethod: "
                    + origPlugin.get(WVPluginManager.KEY_METHOD));
            }
            callmethod.objectName = origPlugin.get(WVPluginManager.KEY_NAME);
            callmethod.methodName = origPlugin.get(WVPluginManager.KEY_METHOD);
        }
        Object classinstance = ((WVWebView) (callmethod.webview)).getJsObject(callmethod.objectName);

        if (classinstance != null) {
            if (classinstance instanceof WVApiPlugin) {
                TaoLog.i(TAG, "call new method execute.");
                callmethod.classinstance = classinstance;
                startCall(CALL_EXECUTE, callmethod);
                return;
            }
            try {
                if (callmethod.methodName != null) {
                    Method method = classinstance.getClass().getMethod(callmethod.methodName, Object.class, String.class);
                    if (method.isAnnotationPresent(WindVaneInterface.class)) {
                        callmethod.classinstance = classinstance;
                        callmethod.method = method;
                        startCall(CALL_METHOD, callmethod);
                        return;
                    } else {
                        TaoLog.w(TAG, "callMethod: Method " + callmethod.methodName + " didn't has @WindVaneInterface annotation, obj="
                            + callmethod.objectName);
                    }
                }
            } catch (NoSuchMethodException e) {
                TaoLog.e(TAG, "callMethod: Method " + callmethod.methodName
                    + " didn't found. It must has two parameter, Object.class and String.class, obj=" + callmethod.objectName);
            }
        } else {
            TaoLog.w(TAG, "callMethod: Plugin " + callmethod.objectName
                + " didn't found, you should call WVPluginManager.registerPlugin first.");
        }
        startCall(NO_METHOD, callmethod);

    }

    public static void startCall(int type, WVCallMethodContext callmethod) {
        Message msg = Message.obtain();
        msg.what = type;
        msg.obj = callmethod;
        mHandler.sendMessage(msg);
    }

    @Override
    public boolean handleMessage(Message msg) {
        WVCallMethodContext callmethod = (WVCallMethodContext) msg.obj;
        if (callmethod == null) {
            TaoLog.e(TAG, "CallMethodContext is null, and do nothing.");
            return false;
        }
        WVCallBackContext callback = new WVCallBackContext((WVWebView) callmethod.webview, callmethod.token, callmethod.objectName,
            callmethod.methodName);
        switch (msg.what) {
            case CALL_EXECUTE: {
                Object object = callmethod.classinstance;
                if (!((WVApiPlugin) object).execute(callmethod.methodName, TextUtils.isEmpty(callmethod.params) ? "{}" : callmethod.params,
                    callback)) {
                    if (TaoLog.getLogStatus()) {
                        TaoLog.w(TAG, "WVApiPlugin execute failed. method: " + callmethod.methodName);
                    }
                    startCall(NO_METHOD, callmethod);
                }
                return true;
            }
            case CALL_METHOD: {
                Object object = callmethod.classinstance;
                try {
                    callmethod.method.invoke(object, callback, TextUtils.isEmpty(callmethod.params) ? "{}" : callmethod.params);
                } catch (Exception e) {
                    TaoLog.e(TAG, "call method " + callmethod.method + " exception. " + e.getMessage());
                }
                return true;
            }
            case NO_METHOD: {
                WVResult result = new WVResult();
                result.setResult(WVResult.NO_METHOD);
                //if (WVMonitorService.getJsBridgeMonitor() != null) {
                //    WVMonitorService.getJsBridgeMonitor().didOccurError(callmethod.objectName, callmethod.methodName, WVResult.NO_METHOD,
                //        callback.getWebview().getUrl());
                //}
                callback.error(result);
                return true;
            }
            case NO_PERMISSION: {
                WVResult result = new WVResult();
                result.setResult(WVResult.NO_PERMISSION);
                //if (WVMonitorService.getJsBridgeMonitor() != null) {
                //    WVMonitorService.getJsBridgeMonitor().didOccurError(callmethod.objectName, callmethod.methodName, WVResult.NO_PERMISSION,
                //        callback.getWebview().getUrl());
                //}
                callback.error(result);
                return true;
            }
            case CLOSED: {
                WVResult result = new WVResult();
                result.setResult(WVResult.CLOSED);
                //if (WVMonitorService.getJsBridgeMonitor() != null) {
                //    WVMonitorService.getJsBridgeMonitor().didOccurError(callmethod.objectName, callmethod.methodName, WVResult.CLOSED,
                //        callback.getWebview().getUrl());
                //}
                callback.error(result);
                return true;
            }
            default:
                break;
        }
        return false;
    }

    /**
     * 获取组件名称
     *
     * @param url
     * @return
     */
    private WVCallMethodContext getRequest(String url) {
        if (url == null) {
            return null;
        }
        Matcher m = pattern.matcher(url);
        if (m.matches()) {
            WVCallMethodContext ctx = new WVCallMethodContext();
            int count = m.groupCount();
            if (count >= 5) {
                ctx.params = m.group(5);
            }
            if (count >= 3) {
                ctx.objectName = m.group(1);
                ctx.token = m.group(2);
                ctx.methodName = m.group(3);
                return ctx;
            }
        }
        return null;
    }

    public void destroy() {
        isInit = false;
    }

}
